package
{
	import com.adobe.serialization.json.JSON;
	import com.adobe.serialization.json.JSONEncoder;
	import com.adobe.utils.DateUtil;
	import com.greensock.TweenMax;
	
	import de.polygonal.math.PM_PRNG;
	
	import flash.display.Sprite;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.DataEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.net.SharedObject;
	import flash.net.XMLSocket;
	import flash.utils.Timer;
	import flash.utils.getTimer;
	
	public class nodeclient extends Sprite
	{
		
		public var id:String;
		public var host:Host;
		public var socket:XMLSocket;
		public var simulation:Simulation;
		public var redSide:Sprite;
		public var blueSide:Sprite;
		public var redTeam:Vector.<Entity>;
		public var blueTeam:Vector.<Entity>;
		
		public function nodeclient()
		{
			redTeam = new Vector.<Entity>;
			blueTeam = new Vector.<Entity>;
			var arena:Sprite = new Sprite();
			blueSide = new Sprite();
			blueSide.graphics.beginFill(0x7777ff);
			blueSide.graphics.drawRect(0,0,500,250);
			blueSide.graphics.endFill();
			arena.addChild(blueSide);
			redSide = new Sprite();
			redSide.graphics.beginFill(0xff7777);
			redSide.graphics.drawRect(0,0,500,250);
			redSide.graphics.endFill();
			arena.addChild(redSide);
			redSide.y = blueSide.height;
			addChild(arena);
			
			
			simulation = new Simulation();
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			prng = new PM_PRNG();
			players = new Vector.<Entity>;
			var date:Date = new Date();
			id = String(date.getTime());  
			mobs = new Vector.<Entity>;
			socket = new XMLSocket("192.168.1.9", 1337);  
			socket.addEventListener(Event.CONNECT, function():void{
				var data:Object = new Object();
				data['command'] = 'connect';
				data['id'] = id;				
				socket.send(JSON.encode(data));
				var findEstablishedHostTimer:Timer = new Timer(2000);
				findEstablishedHostTimer.start();
				findEstablishedHostTimer.addEventListener(TimerEvent.TIMER, function(te:TimerEvent):void{
					findEstablishedHostTimer.stop();
					if(host == null){
						host = new Host();
						host.hostID = id;
						host.guests.push(id);
						startSimulation(getTimer());
					}
				});
				
				var first:Boolean = true;
				socket.addEventListener(DataEvent.DATA, function(dataEvent:DataEvent):void{
					if(first){
						first = false;
						return;
					}
					
					var msg:Object = JSON.decode(dataEvent.data);
					switch(msg['command']){
						case "connect":
							onAnotherUserConnect(msg);
							break;
						case "connectReply":
							processConnectReply(msg);
							break;
						case "click":
							processClick(msg);
							break;
						case "tick":
							processTick(msg);
							break;
					}	
				});  				
			});  
			socket.addEventListener(IOErrorEvent.IO_ERROR, function():void{});  
			
			//addDebugStuff();
		}
		
		private function addDebugStuff():void{
			addDebugButton(0xff0000, new Point(10,10), function():void{
				decreaseUserTick();
			});
			
			addDebugButton(0x0000ff, new Point(10,30), function():void{
				increaseUserTick();
			});			
		}
		
		public var userFPS:Number = 30;
		private function decreaseUserTick():void{
			userFPS *= .5;
			userTick.delay = 1000/userFPS;
		}
		
		private function increaseUserTick():void{
			userFPS *= 2;
			userTick.delay = 1000/userFPS;
		}
		
		private function addDebugButton(color:uint, location:Point, clickCallback:Function):void{
			var redButton:Sprite = new Sprite();
			redButton.graphics.beginFill(color);
			redButton.graphics.drawCircle(0,0,10);
			redButton.graphics.endFill();
			addChild(redButton);
			redButton.x = location.x;
			redButton.y = location.y
			redButton.addEventListener(MouseEvent.CLICK, function(me:MouseEvent):void{
				me.stopImmediatePropagation();
				clickCallback();
			});
		}
		
		private function processClick(msg:Object, onSimulationTick:Boolean = false):void{
			if(onSimulationTick){
				var player:Entity = getPlayer(msg['id']);
				player.moveTo(new Point(msg['x'], msg['y']));				
			} 
			else {
				if(msg['hostId'] == id){
					userActions.push(msg);
				}
			}
		}
		
		private var players:Vector.<Entity>;
		private function createPlayer(id:String):void{
			var player:Entity = new Entity();
			if(redTeam.length < blueTeam.length){
				player.id = id;
				player.initAsPlayer(Entity.TEAM_RED,this.redSide);
				players.push(player);
				addChild(player.display);
				redTeam.push(player);				
			}
			else{
				player.id = id;
				player.initAsPlayer(Entity.TEAM_BLUE,this.blueSide);
				players.push(player);
				addChild(player.display);
				blueTeam.push(player);				
			}

		}
		
		private function getPlayer(id:String):Entity{
			var i:int, il:int;
			for(i = 0, il = players.length; i < il; i++){
				if(players[i].id == id){
					return players[i];
				}
			}
			
			return null;
		}
		
		public var prng:PM_PRNG;
		public var lastTickProcessed:int;
		public var mobs:Vector.<Entity>;
		
		private function processTick(msg:Object):void{

			var nextTickCount:int = msg['tickCounter'];
			prng.seed = msg['seed'];
			simulation.setSimulationTime(msg['time']);
			if(lastTickProcessed + 1 == nextTickCount){
				if(prng.nextIntRange(0,1) == 1){
					var mob:Entity = new Entity();
					mob.initAsMob();
					mobs.push(mob);
					mob.currentLocation = new Point(prng.nextDoubleRange(0, 500),prng.nextDoubleRange(0, 500));
					mob.finalLocation = mob.currentLocation;
					mob.updateDisplay();
					addChild(mob.display);
				}
				
				var userActions:Array = msg['userActions'];
				for each(var msg:Object in userActions){
					switch(msg['command']){
						case "click":
							processClick(msg,true);
							break;
						default:
							throw new Error("Unknown command");
							break;
					}									
				}
			}
			else{
				//trace("mismatch tick" + tickCounter + ":" + nextTickCount);
				throw new Error("Simulation Desync Error");
			}
			
			lastTickProcessed = nextTickCount;
		}
		
		public var tickCounter:int;

		public function getSimulationTime():int{
			return simulation.getSimulationTime();
		}
		
		public var lastTime:int = 0;
		public var userActions:Array = new Array();
		
		private function startSimulation(simulationTime:int):void{
			simulation.setSimulationTime(simulationTime);
			
			if(host.hostID == id){
				var simulationTick:Timer = new Timer(500);
				tickCounter = 1;
				simulationTick.addEventListener(TimerEvent.TIMER, function(te:TimerEvent):void{
					var data:Object = new Object();

					data['command'] = 'tick';
					data['hostId'] = host.hostID;
					data['id'] = id;
					data['time'] = getSimulationTime();
					data['tickCounter'] = tickCounter++;
					data['seed'] = Math.floor(Math.random()*int.MAX_VALUE);
					data['userActions'] = userActions;
					
					socket.send(JSON.encode(data));
					userActions = new Array();
				});
				simulationTick.start();				
			}
			
			createPlayer(id);

			var target:Sprite = new Sprite();
			target.visible = false;
			target.graphics.beginFill(0x00ff00);
			target.graphics.drawCircle(0,0,3);
			target.graphics.endFill();
			addChild(target);
			
			stage.addEventListener(MouseEvent.CLICK, function(me:MouseEvent):void{
				target.visible = true;
				target.scaleX = 6;
				target.scaleY = 6;
				target.x = me.stageX;
				target.y = me.stageY;
				target.alpha = 0;
				TweenMax.to(target,.4,{scaleX: 1, scaleY: 1, alpha: 1});
				
				
				var data:Object = new Object();
				
				data['command'] = 'click';
				data['hostId'] = host.hostID;
				data['id'] = id;
				data['x'] = me.stageX;
				data['y'] = me.stageY;
				data['time'] = getSimulationTime();
				
				socket.send(JSON.encode(data));
			}); 


			
			var lastUserTick:int = getSimulationTime();			
//			stage.addEventListener(Event.ENTER_FRAME, function(e:Event):void{
//				var i:int, il:int;
//				var curTime:int = getSimulationTime();
//				var delta:int = curTime - lastTime;
//				for(i = 0, il = players.length; i < il; i++){
//					players[i].update(delta);
//				}
//				
//				lastTime = curTime;			
//			});
			
			userTick.addEventListener(TimerEvent.TIMER, function(te:TimerEvent):void{
				var i:int, il:int, j:int, jl:int;
				var curUserTick:int = getSimulationTime();
				var delta:int = curUserTick - lastUserTick;
				for(i = 0, il = players.length; i < il; i++){
					players[i].update(delta);
				}
				
				var mobsToDestroy:Vector.<Entity> = new Vector.<Entity>;
				
				for(i = 0, il = mobs.length; i < il; i++){
					for(j = 0, jl = players.length; j < jl; j++){
						var player:Entity = players[j];
						if(player.currentLocation.subtract(mobs[i].currentLocation).length < 15){
							if(mobsToDestroy.indexOf(mobs[i]) == -1){
								mobsToDestroy.push(mobs[i]);
								player.speed++;
							}
						}
						
						
					}
				}
				
				for(i = 0, il = mobsToDestroy.length; i < il; i++){
					mobs.splice(mobs.indexOf(mobsToDestroy[i]),1);
					mobsToDestroy[i].display.parent.removeChild(mobsToDestroy[i].display);
				}
				
				lastUserTick = curUserTick;
			});
			userTick.start();
		}
		public var userTick:Timer = new Timer(1000/30);
		
		private function processConnectReply(msg:Object):void{
			if(msg['id'] == id) return;
			if(getPlayer(msg['id']) != null) return;
			
			if(!host){
				host = new Host();
				host.hostID = msg['hostId'];
				host.guests.push(id);
				lastTickProcessed = tickCounter = msg['tickCounter'] - 1;
			}
			
			if(host.hostID != msg['hostId']){
				throw new Error("Host id mismatch");
			}
			else{
				host.guests.push(msg['id']);
				createPlayer(msg['id']);
			}
			
			if(msg['gameState']){
				updateGameState(JSON.decode(msg['gameState']));
				startSimulation(msg['time']);
			}
		}
		
		private function updateGameState(gameState:Object):void{
			players = Entity.unserializeVector(gameState['players']);
			for each(var player:Entity in players){
				if(player.playerTeam == Entity.TEAM_BLUE){
					player.initAsPlayer(player.playerTeam, blueSide);	
				}
				else{
					player.initAsPlayer(player.playerTeam, redSide);
				}
				addChild(player.display);
			}
			
			mobs = Entity.unserializeVector(gameState['mobs']);
			for each(var mob:Entity in mobs){
				mob.initAsMob();
				addChild(mob.display);
			}			
		}
		
		private function onAnotherUserConnect(msg:Object):void{
			if(msg['id'] == id) return;
			if(getPlayer(msg['id']) != null) return;
			
			host.guests.push(msg['id']);
			createPlayer(msg['id']);
			
			var data:Object = new Object();

			data['command'] = 'connectReply';
			data['id'] = id;
			data['hostId'] = host.hostID;
			data['tickCounter'] = tickCounter;	
			
			if(host.hostID == id){
				data['gameState'] = JSON.encode(getGameState());
							
			}			
			
			socket.send(JSON.encode(data));
		}
		
		
		private function getGameState():Object{
			var gameState:Object = new Object();
			gameState['players'] = Entity.serializeVector(players);
			gameState['mobs'] = Entity.serializeVector(mobs);
				
			return gameState;
		}
	}
}